/**
 * виджет поля ввода, реализующее сервис, подобный Google suggest
 */

;(function($){

// сравнение без учета регистра
  $.extend(String.prototype, {iequals: function(s){return(typeof(s)=="string")&&(this.toLowerCase()==s.toLowerCase());}});

  $.extend($.ui.keyCode, {
    PRNSCR: 44,
    F1    : 112,
    F2    : 113,
    F3    : 114,
    F4    : 115,
    F5    : 116,
    F6    : 117,
    F7    : 118,
    F8    : 119,
    F9    : 120,
    F10   : 121,
    F11   : 122,
    F12   : 123
  });

  $.widget("ui.suggest", {
    options   : {url: "", data: {}, timeout: 1000, selectedClass: "", listClass: ""},
  // конструктор
    _create   : function()
                {
                  var self = $.extend(this, {
                    listbox   : $("<ul />", {
                                  "class": this.options.listClass ? ["listbox", this.options.listClass].join(" ") : "listbox",
                                  "style": "position:absolute;padding:0;margin:0;list-style-type:none;outline:0 none;display:none;z-index:100;"
                                }).hover(function(){$(this).data("capture",true);}, function(){$(this).data("capture",false);}).appendTo(document.body),
                    _request  : null,
                    _timer    : 0,
                    _attrs    : {value: this.element.val(), title: this.element.attr("title")},
                    _onClick  : function(){return self.dropdown(self.listbox.is(":hidden"));},
                    _onBlur   : function(){if(!self.isCaptured())self.dropdown(false);},
                    _onKeyDown: function(e)
                                {
                                  if ((0 <= $.inArray(e.keyCode, [$.ui.keyCode.TAB, $.ui.keyCode.PAGE_UP, $.ui.keyCode.PAGE_DOWN, $.ui.keyCode.PRNSCR])) ||
                                      ($.ui.keyCode.SHIFT <= e.keyCode && e.keyCode <= $.ui.keyCode.CAPS_LOCK) ||
                                      ($.ui.keyCode.F1 <= e.keyCode && e.keyCode <= $.ui.keyCode.F12))
                                    return true;
                                  var visible = self.listbox.is(":visible");
                                  if (0 <= $.inArray(e.keyCode, [$.ui.keyCode.END, $.ui.keyCode.HOME, $.ui.keyCode.LEFT, $.ui.keyCode.RIGHT]))
                                  {
                                    if (self._request)
                                      self._request.abort();
                                    clearTimeout(self._timer);
                                  }
                                  switch (e.keyCode)
                                  {
                                    case $.ui.keyCode.ENTER :
                                      if (visible)
                                        self.selected().click();
                                      else
                                      {
                                        self.select();
                                        break;
                                      }
                                    case $.ui.keyCode.ESCAPE:
                                      if (visible)
                                      {
                                        self.dropdown(false);
                                        return false;
                                      }
                                      break;
                                    case $.ui.keyCode.UP    :
                                      self.selectNext(false);
                                      break;
                                    case $.ui.keyCode.DOWN  :
                                      self.selectNext(true);
                                      break;
                                    default                 :
                                      self._timer = setTimeout(function()
                                      {
                                        var value = $.trim(self.element.val());
                                        if (value)
                                        {
                                          if (!value.iequals(self.element.attr("title")))
                                          {
                                            self.element.attr("title", value).data("id", 0);
                                            self._request = self.dropdown(true, value, function()
                                            {
                                              var self = this;
                                              this.items().each(function()
                                              {
                                                var $this = $(this);
                                                var text  = $this.text();
                                                if (value.iequals(text))
                                                {
                                                  self.element.data("id", this.id);
                                                  self.selected(this);
                                                }
                                                else
                                                  $this.html(text.replace(new RegExp(value, "i"), "<strong>$&</strong>"));
                                              });
                                            });
                                          }
                                        }
                                        else
                                        {
                                          self.element.attr("title", "").data("id", 0);
                                          self.dropdown(false);
                                        }
                                      }, self.options.timeout);
                                  }
                                }
                  });
                  this.element.
                    addClass("combobox").
                    data("id", 0).
                    attr("title", this._attrs.value).
                    click(this._onClick).
                    blur(this._onBlur).
                    keydown(this._onKeyDown);
                },
  // деструктор
    destroy   : function()
                {
                  this.element.
                    unbind("keydown", this._onKeyDown).
                    unbind("blur", this._onBlur).
                    unbind("click", this._onClick).
                    data("id", null).
                    removeClass("combobox").
                    attr(this._attrs);
                  this.listbox.remove();
                },
    isCaptured: function(){return this.listbox.data("capture");},
  // вернуть элементы списка
    items     : function(){return this.listbox.find(".list-item");},
  // вернуть выбранный элемент
    selected  : function(){return this.listbox.find(".selected-item");},
  // выбрать элемент
    select    : function(item)
                {
                  this.selected().removeClass("selected-item").removeClass(this.options.selectedClass);
                  if (item)
                  {
                    $(item).addClass("selected-item").addClass(this.options.selectedClass);
                    var top    = 0;
                    var $items = this.items();
                    var list   = this.listbox[0]
                    if (item != $items[0])
                      if (item == $items[$items.length - 1])
                        top = list.scrollHeight;
                      else
                      {
                        var t  = item.offsetTop;
                        var h  = item.offsetHeight;
                        var oh = list.offsetHeight;
                        top    = list.scrollTop;
                        if ((top + oh) < (t + h))
                          top += oh;
                        if (t < top)
                          top = t - h;
                      }
                    list.scrollTop = top;
                  }
                },
  // раскрыть/скрыть список
    dropdown  : function(show, filter, callback, data)
                {
                  var self = this;
                  if (show)
                  {
                    var params = $.extend({}, this.options.data, {"filter": filter || ""});
                    this.element.triggerHandler("request", params);
                    return $.ajax({
                      url       : this.options.url,
                      data      : params,
                      type      : "get",
                      global    : false,
                      beforeSend: function(){self.element.addClass("load-list");},
                      success   : function(items)
                                  {
                                    if (items)
                                    {
                                      var value = self.element.val();
                                      self.listbox.empty();
                                      for (var i = 0; i < items.length; ++i)
                                      {
                                        var $item = $("<li />").appendTo(self.listbox);
                                        $.each(items[i], function(attr, v)
                                        {
                                          switch (attr.toLowerCase())
                                          {
                                            case "name"    :
                                              if (v == "-")
                                                $item.html("<hr />");
                                              else
                                                $item.text(v);
                                              break;
                                            case "disabled":
                                              if (v)
                                                $item.attr("disabled", "disabled");
                                              break;
                                            default        :
                                              $item.attr(attr, v);
                                          }
                                        });
                                        $item.addClass("list-item");
                                        if (value && value.iequals($item.text()))
                                          $item.addClass("selected-item").addClass(self.options.selectedClass);
                                      }
                                    }
                                  },
                      complete  : function()
                                  {
                                    var $items = self.items();
                                    self.element.triggerHandler("dropdown", !!$items.length);
                                    if ($items.length)
                                    {
                                      $items.filter(":not([disabled])").
                                             hover(function(){self.select(this);}).
                                             click(function()
                                             {
                                               var value = $(this).text();
                                               self.element.val(value).
                                                            attr("title", value).
                                                            data("id", this.id).
                                                            focus().
                                                            select();
                                               self.dropdown(false);
                                               return false;
                                             });
                                      var p  = self.element.offset();
                                      p.top += parseInt(self.element.css("border-top-width")) + self.element.height();
                                      self.listbox.css(p).
                                                   width(self.element.outerWidth() -
                                                         parseInt(self.element.css("border-left-width")) -
                                                         parseInt(self.element.css("border-right-width"))).
                                                   slideDown(function()
                                                   {
                                                     self.select(self.selected()[0]);
                                                     if ($.isFunction(callback))
                                                       callback.call(self, data);
                                                   });
                                    }
                                    else
                                      self.listbox.hide();
                                    self.element.removeClass("load-list");
                                  }
                    });
                  }
                  else
                    this.listbox.slideUp(function()
                    {
                      if ($.isFunction(callback))
                        callback.call(self, data);
                      self.element.triggerHandler("dropdown", false);
                    });
                  return null;
                },
  // выбрать следующий/предыдущий элемент
    selectNext: function(next)
                {
                  if (this.listbox.is(":visible"))
                  {
                    var filter = ".list-item:not([disabled])";
                    var $items = this.items().filter(filter);
                    var $item  = this.selected();
                    var last   = $items.length - 1;
                    this.select((!$item.length || ($item[0] == $items[next ? last : 0])
                                  ? $items[next ? 0 : last]
                                  : $item[next ? "nextAll" : "prevAll"](filter)[0]));
                  }
                  else
                    this.dropdown(true);
                }
  });

})(jQuery);
